﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using WeiboSharp.Authenticators.OAuth;
using WeiboSharp.Extensions;
using WeiboSharp.Models;

namespace WeiboSharp.Authenticators
{
    [Serializable]
    public class OAuth1Authenticator : IAuthenticator
    {
        public string ConsumerKey { get; set; }
        public string ConsumerSecret { get; set; }
        public OAuthParameterHandling ParameterHandling { get; set; }
        public OAuthSignatureMethod SignatureMethod { get; set; }
        public OAuthSignatureTreatment SignatureTreatment { get; set; }
        public OAuthType Type { get; set; }

        public string Token { get; set; }
        public string TokenSecret { get; set; }
        public string Verifier { get; set; }
        public string ClientUsername { get; set; }
        public string ClientPassword { get; set; }
        public string CallbackUrl { get; set; }
        public string Version { get; set; }
        public string SessionHandle { get; set; }

        public static OAuth1Authenticator ForRequestToken(string consumerKey, string consumerSecret)
        {
            var credentials = new OAuth1Authenticator
                                  {
                                      Type = OAuthType.RequestToken,
                                      ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                                      SignatureMethod = OAuthSignatureMethod.HmacSha1,
                                      SignatureTreatment = OAuthSignatureTreatment.Escaped,
                                      ConsumerKey = consumerKey,
                                      ConsumerSecret = consumerSecret
                                  };
            return credentials;
        }

        public static OAuth1Authenticator ForRequestToken(string consumerKey, string consumerSecret, string callbackUrl)
        {
            var credentials = ForRequestToken(consumerKey, consumerSecret);
            credentials.CallbackUrl = callbackUrl;
            return credentials;
        }

        public static OAuth1Authenticator ForAccessToken(string consumerKey, string consumerSecret, string requestToken, string requestTokenSecret)
        {
            var credentials = new OAuth1Authenticator
            {
                Type = OAuthType.AccessToken,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                SignatureTreatment = OAuthSignatureTreatment.Escaped,
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                Token = requestToken,
                TokenSecret = requestTokenSecret
            };
            return credentials;
        }

        public static OAuth1Authenticator ForAccessToken(string consumerKey, string consumerSecret, string requestToken, string requestTokenSecret, string verifier)
        {
            var credentials = ForAccessToken(consumerKey, consumerSecret, requestToken, requestTokenSecret);
            credentials.Verifier = verifier;
            return credentials;
        }

        public static OAuth1Authenticator ForAccessTokenRefresh(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret, string sessionHandle)
        {
            var credentials = ForAccessToken(consumerKey, consumerSecret, accessToken, accessTokenSecret);
            credentials.SessionHandle = sessionHandle;
            return credentials;
        }

        public static OAuth1Authenticator ForAccessTokenRefresh(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret, string sessionHandle, string verifier)
        {
            var credentials = ForAccessToken(consumerKey, consumerSecret, accessToken, accessTokenSecret);
            credentials.SessionHandle = sessionHandle;
            credentials.Verifier = verifier;
            return credentials;
        }

        public static OAuth1Authenticator ForClientAuthentication(string consumerKey, string consumerSecret, string username, string password)
        {
            var credentials = new OAuth1Authenticator
            {
                Type = OAuthType.ClientAuthentication,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                SignatureTreatment = OAuthSignatureTreatment.Escaped,
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                ClientUsername = username,
                ClientPassword = password
            };

            return credentials;
        }

        public static OAuth1Authenticator ForProtectedResource(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret)
        {
            var credentials = new OAuth1Authenticator
            {
                Type = OAuthType.ProtectedResource,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                SignatureTreatment = OAuthSignatureTreatment.Escaped,
                ConsumerKey = consumerKey,
                ConsumerSecret = consumerSecret,
                Token = accessToken,
                TokenSecret = accessTokenSecret
            };
            return credentials;
        }

        public WeiboRequest Authenticate(WeiboClient client, WeiboRequest request)
        {
            var workflow = new OAuthWorkflow
            {
                ConsumerKey = ConsumerKey,
                ConsumerSecret = ConsumerSecret,
                ParameterHandling = ParameterHandling,
                SignatureMethod = SignatureMethod,
                SignatureTreatment = SignatureTreatment,
                Verifier = Verifier,
                Version = Version,
                CallbackUrl = CallbackUrl,
                SessionHandle = SessionHandle,
                Token = Token,
                TokenSecret = TokenSecret,
                ClientUsername = ClientUsername,
                ClientPassword = ClientPassword
            };

            return AddOAuthData(client, request, workflow);
        }

        private WeiboRequest AddOAuthData(WeiboClient client, WeiboRequest request, OAuthWorkflow workflow)
        {
            var url = client.BuildUri(request).ToString();

            OAuthWebQueryInfo oauth;

            var method = request.Method;
            var parameters = request.Parameters;
            switch (Type)
            {
                case OAuthType.RequestToken:
                    workflow.RequestTokenUrl = url;
                    oauth = workflow.BuildRequestTokenInfo(method, parameters);
                    break;
                case OAuthType.AccessToken:
                    workflow.AccessTokenUrl = url;
                    oauth = workflow.BuildAccessTokenInfo(method, parameters);
                    break;
                case OAuthType.ClientAuthentication:
                    workflow.AccessTokenUrl = url;
                    oauth = workflow.BuildClientAuthAccessTokenInfo(method, parameters);
                    break;
                case OAuthType.ProtectedResource:
                    oauth = workflow.BuildProtectedResourceInfo(method, parameters, url);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            switch (ParameterHandling)
            {
                case OAuthParameterHandling.HttpAuthorizationHeader:
                    request.AddParameter(new OAuthParameter("oauth_signature", oauth.Signature));
                    request.AddHeader("Authorization", GetAuthorizationHeader(parameters));
                    break;
                case OAuthParameterHandling.UrlOrPostParameters:
                    request.AddParameter(new OAuthParameter("oauth_signature", HttpUtility.UrlDecode(oauth.Signature)));
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return request;
        }

        private string GetAuthorizationHeader(List<Parameter> parameters)
        {
            var sb = new StringBuilder("OAuth ");

            parameters.Sort(new ParameterComparer());

            var parameterCount = 0;
            foreach (var parameter in parameters.Where(parameter =>
                                                       !string.IsNullOrWhiteSpace(parameter.Name) &&
                                                       !string.IsNullOrWhiteSpace(parameter.Value) &&
                                                        parameter.Type == ParameterType.OAuth
                                                       ))
            {
                parameterCount++;
                var format = parameterCount < parameters.Count ? "{0}=\"{1}\"," : "{0}=\"{1}\"";
                sb.Append(format.FormatWith(parameter.Name, parameter.Value));
            }

            var authorization = sb.ToString();
            return authorization;
        }
    }
}


